View Javadoc

1   /*
2    * SAMLAuthZServiceBase.java
3    *
4    * Created on January 5, 2005, 5:50 PM
5    * 
6    */
7   
8   package org.opensciencegrid.authz.service;
9   
10  
11  import java.util.ArrayList;
12  import java.util.Iterator;
13  import java.util.Calendar;
14  import java.rmi.RemoteException;
15  import org.glite.security.SecurityContext;
16  import org.glite.security.util.axis.InitSecurityContext;
17  
18  import org.opensaml.v1_0_1.XML;
19  import org.opensaml.v1_0_1.QName;
20  import org.opensaml.v1_0_1.SAMLDecision;
21  import org.opensaml.v1_0_1.SAMLException;
22  import org.opensaml.v1_0_1.SAMLSubject;
23  import org.opensaml.v1_0_1.SAMLRequest;
24  import org.opensaml.v1_0_1.SAMLAssertion;
25  import org.opensaml.v1_0_1.SAMLResponse;
26  import org.opensaml.v1_0_1.SAMLAuthorizationDecisionQuery;
27  import org.opensaml.v1_0_1.SAMLAuthorizationDecisionStatement;
28  import org.opensaml.v1_0_1.SAMLAttribute;
29  import org.opensaml.v1_0_1.SAMLAttributeStatement;
30  import org.opensaml.v1_0_1.SAMLAction;
31  
32  import org.opensciencegrid.authz.saml.XACMLObligation;
33  import org.opensciencegrid.authz.saml.ObligatedAuthorizationDecisionStatement;
34  import org.opensciencegrid.authz.saml.OSGXML;
35  import org.opensciencegrid.authz.saml.SAMLExtensionInit;
36  import org.opensciencegrid.authz.saml.SAMLUtil;
37  
38  import org.opensciencegrid.authz.stubs.SAMLRequestPortType;
39  import org.opensciencegrid.authz.stubs.SAMLRequestType;
40  import org.opensciencegrid.authz.stubs.SAMLResponseType;
41  
42  import org.opensciencegrid.authz.common.OSGAuthorizationConstants;
43  
44  import org.apache.log4j.Category;
45  import org.apache.axis.message.MessageElement;
46  import org.w3c.dom.Element;
47  
48  /*** 
49   * Common implementation for a SAML authorization service: parses the requests,
50   * and performs the authorization through an abstract method, to be implemented
51   * by the AuthZ service.
52   *
53   * Status: untested
54   *
55   * TODO: retrieving the caller identity from the trustmanager
56   * 
57   * @author Markus Lorch, Gabriele Carcassi, John Weigand
58   */
59  
60  public abstract class SAMLAuthZServiceBase implements SAMLRequestPortType {
61  
62  
63      /*** This inner class is used as return value from the authorize method */
64  
65      protected class AuthzDecision {
66          
67          /*** decision may have the following values 
68            * org.opensaml.v1_0_1.SAMLDecision.INDETERMINATE, SAMLDecision.DENY, SAMLDecision.PERMIT,
69            * is initialized to SAMLDecision.INDETERMINATE
70            */
71          public String decision = SAMLDecision.INDETERMINATE;       
72      
73          /*** set of actions authorized by the authorize method */   
74          public ArrayList actions;
75  
76          /*** set of obligations imposed on the response by the authorize method */
77          public ArrayList obligations;
78      
79          /*** holds the issuer name of the authorization decision to be embedded in the  SAMLResponse */
80          public String issuer;
81  
82          /*** method to print the contents for debugging */
83          public String toString() {
84             String s;
85             s = "Decision: "+decision;
86             s = s.concat(", Actions: ");
87             if(actions==null ) s=s.concat("null");
88             else if(actions.isEmpty()) s= s.concat("empty");
89             else
90               for(int i=0;i<actions.size();i++) {
91                 Object x = actions.get(i); 
92                 if (x instanceof SAMLAction) {
93                    SAMLAction a = (SAMLAction) x;
94                    s=s.concat(a.getNamespace()+" "+a.getData()+" | ");
95                 }
96                 else
97                    s= s.concat(actions.get(i).toString()+" | ");
98               }
99             s=s.concat(", Obligations: ");
100            if(obligations==null) s=s.concat("null");
101            else if(obligations.isEmpty()) s= s.concat("empty");
102            else
103              for(int i=0;i<obligations.size();i++) {
104                Object x = obligations.get(i); 
105                if (x instanceof XACMLObligation) {
106                   XACMLObligation o = (XACMLObligation) x;
107                   s=s.concat(o.getObligationId()+" "+o.getAttributeId()+"="+o.getValue())+" | ";
108                }
109                else
110                   s= s.concat("Unsupported Obligation | ");
111              }
112            s = s.concat(", Issuer: "+issuer);
113            return s;
114         }
115 
116     }
117 
118     /*** the Fully Qualified Attribute Name (FQAN) = the VOMS VO-membership/role attribute */
119     protected class FQAN {
120        String issuer;
121        String data;
122     }
123 
124 
125     // static parameters
126 
127     /*** this service can only respond with a SAML AuthorizationDecisionStatement or an 
128         ObligatedAuthorizationDecisionStatement (this form is an OSG SAML Extension) */
129      
130     private static final QName AUTHZ_DECISION_STMT =  new QName(XML.SAML_NS,"AuthorizationDecisionStatement");
131     private static final QName OBLIG_DECISION_STMT =  new QName(OSGXML.SAML_EXT_NS,"ObligatedAuthorizationDecisionStatement");
132 
133     /*** determins for how long (in minutes) should issued assertions be valid */ 
134     private static final int ASSERTION_VALIDITY_IN_MINUTES = 10;
135 
136     /*** Log4Java logger */
137     static Category log = Category.getInstance(SAMLAuthZServiceBase.class.getName() );
138 
139     
140 
141     //##########################################################################################################
142     // Methods
143 
144 
145     /*** Performs the authorization of the request.
146      *
147      * The abstract authorize method must be implemented by an a concrete subclass that implements an authorization 
148      * service. At a minimum the implementation should:
149      *  - evaluate subject, resource and requested actions
150      *  - return SAMLDecision.DENY, SAMLDecision.PERMIT, or SAMLDecision.INDETERMINATE
151      *    if a decision could not be reached (e.g. service not authoritative for resource)
152      *  - set member variable responseIssuer
153      *  - set member variable responseActions (authorized actions if Decision=Permit, otherwise
154      *    a copy of the requested actions)
155      *  in addition it may
156      *  - evaluate presented subject evidence
157      *    (e.g. VOMS FQAN, other privilege attributes, or certificate chain)
158      *  - create XACMLObligation objects and add tehm to the  
159      *    responseObligations member variable
160      */
161     protected abstract AuthzDecision authorize(SAMLSubject subject, String resource, Iterator actions, Iterator evidence)
162            throws SAMLException;
163 
164 
165     /*** Main function, recives SAMLRequest and response with a SAMLResponse 
166      * 1. parse request, can request be serviced by this code?
167      * 2. parse authorization query 
168      * 3. call authorize method implementation of concrete subclass, which will return decision object 
169      * 4. verify and process authorization response from authorize method
170      * 5. create and return SAML response
171      *    if obligations were provided return ObligatedAuthorizationDecisionStatement   
172      *    else return a standard AuthorizationDecisionStatement with the appropriate decision
173      */
174 
175     public SAMLResponseType SAMLRequest(SAMLRequestType samlRequestType) throws java.rmi.RemoteException {
176        
177         /*** the SAMLRequest we are processing */
178         SAMLRequest request = null;
179 
180         /*** the originator of the request */
181         String requestIssuer = null;
182 
183         /*** the authorization decision query from the saml request */
184         SAMLAuthorizationDecisionQuery authzQuery = null;
185   
186         /*** true if we can use ObligatedAuthorizationDecisionStatement to respond */
187         boolean respondWithObligAuthzStmt = false;
188 
189         /*** the AuthzDecision object is used to hold the return values set by the
190             implementation of the abstract authorize method */
191         AuthzDecision response = null;
192 
193         /*** the set of samlStatements that hold the authorization decision - typically one */
194         ArrayList   decisionStmts = null;
195 
196         /*** the set of assertions that hold the decisionStmts - typically one*/
197         ArrayList   responseAssertions = null;
198         
199         /*** the generated samlResponse */     
200         SAMLResponseType samlResponse = null;  
201     
202   
203 
204         try {
205 
206             //Initialize SAML Extensions
207             SAMLExtensionInit.init();
208 
209             // retrieve the request originator 
210 
211             // *************************  todo when glite trusmanager is fixed
212             // whats the equivalent of this call without globus
213             //requestIssuer = SecurityManager.getManager().getCaller();
214             requestIssuer = SecurityUtil.retrieveClientDN();
215             log.debug("Processing incoming SAMLRequest from " + requestIssuer);
216 
217 
218             //#######################################################
219             // parse and process request
220 
221             // parse request
222             request = processSAMLRequest(samlRequestType);
223 
224             // check if we may respond with an ObligatedAuthorizationDecisionStatement              
225             respondWithObligAuthzStmt = checkRespondWith(request);
226 
227             // check if request is an AuthorizationDecisionQuery and extract query
228             authzQuery = extractAuthorizationDecisionQuery(request); 
229 
230             if(authzQuery!=null) log.debug("Extracted authorization decision query");
231           
232             //#######################################################
233             // call implementation of authorize method in concrete subclass
234             log.debug("Calling implementation of authorize method in the authoirzation service subclass");
235             
236 	    response = authorize( 	authzQuery.getSubject(), 
237             				authzQuery.getResource(), 
238            				authzQuery.getActions(),  
239           				authzQuery.getEvidence()
240                                         );
241             log.debug("Authorize method returned:"+ response.toString());
242 
243 
244             //#######################################################
245             // check response from authorize method implementation
246 
247             if (response.decision !=null &&
248                   ( response.decision.equals(SAMLDecision.PERMIT) ||
249                     response.decision.equals(SAMLDecision.DENY) ||
250                     response.decision.equals(SAMLDecision.INDETERMINATE) ) 
251                ) {
252                 // decision value is ok, do nothing
253             }
254             else { 
255               // decision value not allowed, abort 
256               throw new Exception("Authorize method did not return valid decision value");
257             }
258             
259             if (response.actions == null || response.actions.isEmpty()) 
260               throw new Exception("Authorize method did not return any actions");
261             
262             if (response.issuer == null || response.issuer.length() ==0)
263               throw new Exception("Authorize method did not return issuer value");
264             
265        
266             // decide if, in the presence of obligations, an ObligatedAuthorizationDecisionStatement
267             // may be returned based on the clients respondWith wishes, if not we return "indeterminate"
268 
269             if(    (response.obligations!=null) 
270                 	&& (!response.obligations.isEmpty())  
271                 	&& (!respondWithObligAuthzStmt)  ) {
272 
273                     response.obligations = null; // cannot return obligations
274                     log.warn("Response requires obligations, but the set of client-requested response formats did not include ObligatedAuthoirzationDecisionStatement");
275                     log.warn("Must return INDETERMINATE, as obligations cannot be conveyed with standard response"); 
276                     response.decision = SAMLDecision.INDETERMINATE;   // cannot say yes or no
277 
278             } // end if response.obligations
279 
280 
281             //#######################################################
282             // create SAML Response 
283                     
284             log.debug("returning AuthorizationDecisionStatement with decision= "+ response.decision); 
285 
286             decisionStmts =  createAuthzDecisionStmt(authzQuery, response.decision, response.actions, response.obligations);
287 
288             responseAssertions = createAssertions(response.issuer, decisionStmts);
289 
290             samlResponse = createSAMLResponse(  request.getId(), 
291      						requestIssuer,
292 						responseAssertions,     
293      						null
294                                              );
295 
296             log.debug("Returning response");
297 
298             return samlResponse;
299 
300         } catch (Exception e) {
301             // if an error occurred we will reply with a SAMLAuthorizationDecision  
302             // and set decision=indeterminate 
303             // we deliberately do not tell the possible attacker why our code produced an exception
304             log.error("ABORT: "+e);
305             log.debug("Exception trace: ",e);
306             try {
307               log.debug("Attempting to construct a SAML AuthoirzationDecisionStatement with decision = INDETERMINATE");
308               if (response==null) response = new AuthzDecision();
309               if (response.actions == null) {
310                   response.actions = new ArrayList(1);
311                   log.debug("don't have actions set so we will duplicate requested actions");
312                   Iterator ai = authzQuery.getActions();
313                   while(ai.hasNext()) response.actions.add(ai.next());  
314               }
315               if (response.issuer == null) response.issuer="Unknown";
316               decisionStmts = createAuthzDecisionStmt(authzQuery, SAMLDecision.INDETERMINATE, response.actions, null); 
317               responseAssertions = createAssertions(response.issuer, decisionStmts);
318               samlResponse = createSAMLResponse(  request.getId(), 
319      						  requestIssuer,
320 						  responseAssertions,     
321      						  null
322                                                );
323               log.error("Responding with SAML AuthorizationDecisionSatement with decision = INDETERMINATE");
324               return samlResponse;
325 
326             } catch (Exception samlExcp) {
327               log.error("Unable to create a SAML AuthorizationDecisionStatement with decision = INDETERMINATE");
328               log.error("Caught exception was: "+samlExcp,samlExcp);
329               try {
330                 SAMLException reportedException = new SAMLException(new QName(XML.SAMLP_NS, "RequestDenied"), 
331                              "Please contact the system administrator of this authorization service for more information");
332                 samlResponse = createSAMLResponse(request.getId(), requestIssuer, null, reportedException);
333                 log.error("Responding with SAML-Exception response: RequestDenied");
334                 return samlResponse;
335               }
336               catch (Exception samlExcp2) {
337                 // we can't do anything else than throw a remote Exception at this point
338                 // we will not tell the client what kind of error occurred
339                 log.error("Unable to respond to request");
340                 log.error("Caught exception was: "+samlExcp,samlExcp);
341                 throw new java.rmi.RemoteException("Service unable to respond to request!");
342               } // end samlExcp2
343 
344             } // enc catch samlExcp
345         }
346 
347 
348     } // end SAMLRequest
349 
350 
351 
352     //################################################################
353     /*** Parses and returns the SAMLRequest in the RequestType 
354       * throws RemoteException or SAMLException if problems occur
355       */
356     private SAMLRequest processSAMLRequest(SAMLRequestType requestType)
357     throws RemoteException, SAMLException {
358         
359         SAMLRequest returnValue;
360 
361         // --------------------
362         // Verify request type
363         // --------------------
364         log.debug("Validating request type");
365         if (requestType == null) {
366             throw new RemoteException("Received a null request");
367         }
368 
369         // ------------------------------
370         // Construct SAMLRequest object from DOM.
371         // sets this.request
372         // ------------------------------
373         log.debug("Extracting SAMLRequest");
374         try {
375             MessageElement[] msgElement = requestType.get_any();
376             returnValue = new SAMLRequest(msgElement[0].getAsDOM());
377             if (returnValue == null) {
378                 throw new RemoteException("SAMLRequest returned null");
379             }
380         } catch (Exception exp) {
381             throw new RemoteException("Error extracting SAML Request object: "+exp);
382         }
383 
384         return returnValue;
385 
386     } // end method processSAMLRequest
387 
388 
389     //################################################################
390     /*** checks the respondWith request by the client, returns true
391       * if we can respond with an ObligatedAuthorizationDecisionStatement
392       * Note: If no respondWith is present, then true is also returned 
393       * throws RemoteException or SAMLException if problems occur or
394       * if a SAML AuthorizationDecisionStatement is not supporte by
395       * the client
396       */
397     private boolean checkRespondWith(SAMLRequest request)
398     throws RemoteException, SAMLException {
399         // -------------------------------------------------------------------
400         // Validate respondWith elements this service can respond with
401         // the obligated authorization decision statement is optional
402         // the standard authorization decision statmenent is required
403         // -------------------------------------------------------------------
404         boolean authzDecisionStmtFound = false;
405         boolean obligAuthzDecisionStmtFound = false;
406         Iterator respondWiths = request.getRespondWiths();
407 
408         if (respondWiths == null ) {
409            log.debug("No respondWith elements found in request, enabling ObligatedAuthorizationDecisionStatement");
410            obligAuthzDecisionStmtFound = true;
411         } else {
412 
413         // ok respondWiths are present, now lets see whats requested
414       
415         // --- loop thru and check 
416         while (respondWiths.hasNext()) {
417           QName respondWith = (QName)respondWiths.next();
418           // standard SAML AuthzDecisionStatement
419           if (respondWith.equals(this.AUTHZ_DECISION_STMT) ) {
420               authzDecisionStmtFound = true;
421               log.debug("Supported respondWith: "+respondWith.toString());
422           } 
423           else if (respondWith.equals(this.OBLIG_DECISION_STMT) ) {
424               obligAuthzDecisionStmtFound = true;
425               log.debug("Supported respondWith: "+respondWith.toString());
426           }
427           else {
428             log.debug("Found unrecognized respondWith: "+respondWith.toString());
429           } // end if / else if / else
430         } // end while
431 
432         if (!authzDecisionStmtFound) {
433            log.debug("Standard SAML AuthorizationDecisionStatement is not supported by client, aborting");
434            throw new java.rmi.RemoteException("Standard SAML AuthorizationDecisionStatement is not supported by client.");
435         }
436 
437         } // end else respondWiths == null
438 
439         return obligAuthzDecisionStmtFound;
440 
441      } // end method checkRespondWith
442 
443 
444 
445     //################################################################
446     /*** Extracts an authorization decision query from the request
447       * throws RemoteException or SAMLException if problems occur
448       */
449     private SAMLAuthorizationDecisionQuery extractAuthorizationDecisionQuery(SAMLRequest request)
450     throws RemoteException, SAMLException {
451 
452         Object obj = request.getQuery();
453         if (obj instanceof SAMLAuthorizationDecisionQuery ) {
454             log.debug("FOUND SAMLAuthorizationDecisionQuery: "+obj);
455             return (SAMLAuthorizationDecisionQuery) obj;
456         } else {
457             log.error("Request not supported: "+obj);
458             throw new java.rmi.RemoteException("Request not supported.");
459         } //end if/else
460 
461     } //end method extractAuthorizaitonDecisionQuery
462 
463 
464     //#############################################################
465     /*** Returns the Distinguished Name of a SAMLSubject.
466       * Needs: X.509 format validation
467       */
468     private String getRequestUser(SAMLSubject subject)
469     throws SAMLException {
470       String subjectDN = subject.getName().getName();
471       return subjectDN;
472     } // end method
473    
474  
475     //###############################################################
476     /*** Creates an ArrayList with a either a standard SAML AuthorizationDecisionStatement
477       * or an ObligatedAuthorizationDecisionStatement based on the
478       * value of the responseObligations parameter
479       */
480     private ArrayList createAuthzDecisionStmt(SAMLAuthorizationDecisionQuery query,
481                                                String decision,
482 					       ArrayList actions,
483                                                ArrayList obligations)
484     throws SAMLException {
485      
486        ArrayList	samlStmts = new ArrayList(1);
487        Object 	stmt;
488          
489        if(    (obligations!=null) && (!obligations.isEmpty() ) ) // obligations present
490          stmt = new ObligatedAuthorizationDecisionStatement(query.getSubject(),
491 						           query.getResource(),
492                                                            decision,
493                                                            actions,
494                                                            null,
495                                                            obligations);
496        else // no obligations - standard statement
497            stmt = new SAMLAuthorizationDecisionStatement(query.getSubject(),
498 						           query.getResource(),
499                                                            decision,
500                                                            actions,
501                                                            null);
502     
503       samlStmts.add(stmt);
504   
505       return samlStmts;
506 
507     } // end method 
508 
509 
510   
511 
512  
513     //###############################################################
514     /*** Creates an arraylist of SAMLAssertions that hold a single
515       * assertion with the provided SAMLStatements and a validity
516       * time constraint from now for ASSERTION_VALIDITY_IN_MINUTES
517       * minutes
518       */
519     private ArrayList createAssertions(String issuer, ArrayList stmts)
520     throws SAMLException {
521 
522      ArrayList assertions = new ArrayList(1);
523      SAMLAssertion samlAssertion;
524    
525      Calendar notBefore = Calendar.getInstance();
526      Calendar notAfter = Calendar.getInstance();
527      notAfter.add(Calendar.MINUTE, this.ASSERTION_VALIDITY_IN_MINUTES);
528           
529      samlAssertion = new SAMLAssertion(issuer,
530                                        notBefore.getTime(),
531                                        notAfter.getTime(), 
532      				       null, 
533 				       null, 
534 				       stmts);
535      
536      assertions.add(samlAssertion);
537 
538      return assertions;
539 
540    } // end method
541 
542    
543     //###############################################################
544     /*** Creates the SAMLResponseType holding the assertions 
545       */
546     private SAMLResponseType createSAMLResponse(String inResponseTo, 
547 						String recipient, 
548 						ArrayList assertions, 
549 						SAMLException samlException)
550     throws SAMLException {
551 
552       SAMLResponseType samlResponse = null;
553 
554       SAMLResponse response = new SAMLResponse(inResponseTo, recipient, assertions, samlException);
555 
556       log.debug("Created SAMLResponse: "+response);
557 
558       samlResponse = new SAMLResponseType();
559 
560       samlResponse.set_any(new MessageElement[] { new MessageElement((Element)response.toDOM())});
561 
562       return samlResponse;
563 
564     } // end method
565 
566 
567 
568   /*** checks if a specific SAMLAttributeStatement holds a FQAN, and
569     * returns that FQAN attribute in form of a string 
570     * returns null if no FQAN (string) attribute could be located
571     */
572 
573    protected String getFQAN(SAMLAttributeStatement stmt) throws SAMLException {
574 
575       String fqan = null;
576 
577       Iterator attributeIterator = stmt.getAttributes();
578 
579       while (attributeIterator.hasNext()) {
580         Object attrib = attributeIterator.next();
581         if ( ! (attrib instanceof SAMLAttribute) ) {
582           continue;
583         }
584         else {
585           String attrName = ((SAMLAttribute)attrib).getName();
586           String attrNamespace = ((SAMLAttribute)attrib).getNamespace();
587           log.debug("Found Evidence Attribute:  "+attrName+ " with namespace: "+attrNamespace);
588 
589           if ( attrName.equals("FQAN") && attrNamespace.equals(OSGAuthorizationConstants.AUTHZ_NS) ) {
590             fqan = getAttributeValue( (SAMLAttribute) attrib);
591           }
592        } // end if instance of attribute
593       } // end while attribute
594 
595       if(fqan!=null) log.debug("Found FQAN Attribute: "+fqan);
596       else log.warn("Found unsupported FQAN Attribute (it had no string value!!!)");
597       return fqan;
598 
599    }
600 
601 
602    /*** Searches the Evidence elements for FQAN attributes.
603      *  Returns the first FQAN found that matches the querySubject
604      *  These are what GUMS needs to perform the authorization and
605      *  determine the username.
606      */
607 
608      protected FQAN findFQANinSubjectEvidence(Iterator evidenceIterator, SAMLSubject querySubject)
609         throws SAMLException {
610 
611       FQAN fqan = new FQAN();
612 
613       while (evidenceIterator.hasNext()) {
614           Object obj = evidenceIterator.next();
615           if (obj instanceof SAMLAssertion) {
616             // who is the issuer of the statement
617             fqan.issuer = ( (SAMLAssertion) obj).getIssuer();
618 
619             Iterator evidenceStatementIterator = ((SAMLAssertion)obj).getStatements();
620             while (evidenceStatementIterator.hasNext()) {
621               Object stmt = evidenceStatementIterator.next();
622               if (stmt instanceof SAMLAttributeStatement) {
623                 if (SAMLUtil.samlSubjectMatch(querySubject, ((SAMLAttributeStatement)stmt).getSubject() ) ) {
624                    // subject matches
625                    fqan.data=getFQAN((SAMLAttributeStatement)stmt);
626                    // was it a fqan attribute, if not getFQAN will have returned null
627                    if(fqan.data!=null) {
628                      return fqan;
629                    }
630 
631                 }
632               } // end if instance of SAMLattribute
633             } // end while evidence
634           } // end if SAMLAssertion
635         } // end while evidenceIterator
636 
637       return null; // no fqan found in evidence
638 
639     }
640 
641 
642   /*** returns the subset of the requestedActions that are present in the
643     * permissibleActions parameter 
644     */
645 
646    protected ArrayList locatePermissibleActions(Iterator requestedActions, ArrayList permissibleActionsList) {
647 
648      ArrayList permittedActions = new ArrayList(1);
649 
650      log.debug("Locating permissible actions");
651  
652      // loop through all requested actions, check every one against the list of permissible actions
653      while (requestedActions.hasNext()) {
654              SAMLAction ra = (SAMLAction)requestedActions.next();
655          //log.debug("Requested action is: "+ra);
656          for(int i=0;i<permissibleActionsList.size();i++) {
657             SAMLAction pa = (SAMLAction)permissibleActionsList.get(i);
658                           
659              //log.debug("Comparing to permissible action: "+pa);
660 
661              if(SAMLUtil.samlActionMatch(pa,ra) ) {
662                    log.debug("requested action matched permissible action: "+ra);
663                    // requested action is a permissible action, so add it to the permittedActions
664                    permittedActions.add(ra);
665                    // remove this action from the list of permissible actions, so we won't add it
666                    // to the set of permittedActions multiple times, even if it was requested mult. times
667                    permissibleActionsList.remove(i);
668                    // do not check remainder of this set, break
669                    break;
670              } // end if
671 
672           } // end for
673 
674       } // end while
675 
676       // return null if no actions were permitted
677       if (permittedActions.isEmpty() ) return null;
678       
679       return permittedActions; 
680  
681    }
682     
683 
684     //#############################################################
685     /*** Returns the first string value of an attribute,
686      * returns null if not string attribute found
687      */
688     private String getAttributeValue(SAMLAttribute attrib)
689     throws SAMLException {
690 
691       String value = null;
692       Iterator attributeValues = ((SAMLAttribute)attrib).getValues();
693       while (attributeValues.hasNext()) {
694         Object attributeValue = attributeValues.next();
695         if( ! (attributeValue instanceof String)) {
696           continue; // not a string, look at next value
697         }
698         value = (String) attributeValue;
699         break; // no need to look further       
700       } // end while
701 
702       return value;
703     } // end method
704 
705 
706 } // end class SAMLAuthZServiceBase